package org.ljy.lotterysystem.service.mq;


import cn.hutool.core.date.DateUtil;
import org.ljy.lotterysystem.common.exception.ServiceException;
import org.ljy.lotterysystem.common.utils.JacksonUtil;
import org.ljy.lotterysystem.common.utils.MailUtil;
import org.ljy.lotterysystem.common.utils.SMSUtil;
import org.ljy.lotterysystem.controller.param.DrawPrizeParam;
import org.ljy.lotterysystem.dao.dataobject.ActivityPrizeDO;
import org.ljy.lotterysystem.dao.dataobject.WinningRecordsDO;
import org.ljy.lotterysystem.dao.mapper.ActivityPrizeMapper;
import org.ljy.lotterysystem.dao.mapper.IWinningRecordsMapper;
import org.ljy.lotterysystem.service.IDrawPrizeService;
import org.ljy.lotterysystem.service.activitystatus.IActivityStatusManager;
import org.ljy.lotterysystem.service.dto.ConvertActivityStatusDTO;
import org.ljy.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import org.ljy.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import org.ljy.lotterysystem.service.enums.ActivityStatusEnum;
import org.ljy.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisPipelineException;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.ljy.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;


@RabbitListener(queues = QUEUE_NAME)
@Component
public class MqReceiver {

    private static final Logger logger = LoggerFactory.getLogger(MqReceiver.class);
    
    @Autowired
    private IDrawPrizeService drawPrizeService;
    
    @Autowired
    private IActivityStatusManager activityStatusManager;

    @Qualifier("asyncServiceExecutor")
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private SMSUtil smsUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private IWinningRecordsMapper winningRecordsMapper;


    @RabbitHandler
    public void process(Map<String,String> message) {
        //1.接收到消息
        logger.info("MQ消费到消息，message: {}", JacksonUtil.writeValueAsString(message));
        String value = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(value, DrawPrizeParam.class);

        //处理抽奖的逻辑
        try {
            //3.检查抽奖参数是否有效
            if(!drawPrizeService.checkDrawPrizeValid(param)) {
                return;
            }
            //4.扭转状态处理
            statusConvert(param);
            //5.保存中奖者名单
            List<WinningRecordsDO> winningRecordsDOList =  drawPrizeService.saveWinnerRecords(param);
            //6.通知中奖者(短信或者邮箱)
            //使用异步的方式进行发送(同时发生邮箱和短信)
            syncExecute(winningRecordsDOList);
        }catch (ServiceException e){
            logger.error("MQ处理消息异常，{}:{}",e.getCode(),e.getMessage(),e);
            //异常回滚
            rollback(param);
            throw e;
        }catch (Exception e){
            logger.error("MQ处理消息异常!!",e);
            rollback(param);
            throw e;
        }
    }

    /**
     * 处理异常回滚
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        //1.回滚状态(活动、奖品、人员)
        if(!statusNeedRollback(param)) {
            //(1)不需要回滚
            return;
        }
        //(2)需要回滚
        rollbackStatus(param);
        //2.回滚中奖者名单
        if(!winnerNeedRollback(param)) {
            //不需要回滚
            return;
        }
        //需要回滚
        rollbackWinner(param);
    }

    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(),param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        //如何判断？如果活动下的奖品有中奖者，说明需要回滚（活动Id+奖品id组成唯一索引）
        int count = winningRecordsMapper.count(param.getActivityId(),param.getPrizeId());
        return count > 0;
    }

    private void rollbackStatus(DrawPrizeParam param) {
        //具体代码在IActivityStatusManager接口下
        ConvertActivityStatusDTO statusConvertDTO = new ConvertActivityStatusDTO();
        statusConvertDTO.setActivityId(param.getActivityId());
        statusConvertDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        statusConvertDTO.setPrizeId(param.getPrizeId());
        statusConvertDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        statusConvertDTO.setUserIds(param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        statusConvertDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackHandlerEvent(statusConvertDTO);
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        //只需要判断人员或者奖品的状态是否被扭转过，就能知道是否需要回滚
        //状态发送改变，就需要扭转
        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByAPId(param.getActivityId(),param.getPrizeId());
        //状态相等说明需要回滚
        return activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    /**
     * 并发处理抽奖后续流程（通知中奖者）
     * @param winningRecordsDOList
     */
    private void syncExecute(List<WinningRecordsDO> winningRecordsDOList) {
        //1.短信
        threadPoolTaskExecutor.execute(() -> sendMessage(winningRecordsDOList));
        //2.邮箱
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordsDOList));
    }

    /**
     * 发送邮件
     * @param winningRecordsDOList
     */
    private void sendMail(List<WinningRecordsDO> winningRecordsDOList) {
        if(CollectionUtils.isEmpty(winningRecordsDOList)) {
            logger.info("中奖列表为空，不需要发送邮箱");
            return;
        }
        for(WinningRecordsDO winningRecordsDO : winningRecordsDOList) {
            String content = "Hi，" + winningRecordsDO.getWinnerName() + "。恭喜你在"
                    + winningRecordsDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordsDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordsDO.getPrizeName() + "。获奖时间为"
                    + DateUtil. formatTime (winningRecordsDO.getWinningTime()) + "，请尽快领取您的奖励！";
            mailUtil.sendSampleMail(winningRecordsDO.getWinnerEmail(),"中奖通知",content);
        }
    }

    /**
     * 发送短信
     * @param winningRecordsDOList
     */
    private void sendMessage(List<WinningRecordsDO> winningRecordsDOList) {
        if(CollectionUtils.isEmpty(winningRecordsDOList)) {
            logger.info("中奖列表为空，不需要发送邮箱");
            return;
        }
        for (WinningRecordsDO winningRecordsDO : winningRecordsDOList) {
            Map<String, String> templateParam = new HashMap<>();
            templateParam.put("name", winningRecordsDO.getWinnerName());
            templateParam.put("activityName", winningRecordsDO.getActivityName());
            templateParam.put("prizeTiers", ActivityPrizeTiersEnum.forName(winningRecordsDO.getPrizeTier()).getMessage());
            templateParam.put("prizeName", winningRecordsDO.getPrizeName());
            templateParam.put("winningTime",DateUtil. formatTime (winningRecordsDO.getWinningTime()));
            String str = JacksonUtil.writeValueAsString(templateParam);
            smsUtil.sendMessage("SMS_475285155",winningRecordsDO.getWinnerPhoneNumber().getValue(),str);
        }
    }

    /**
     * 状态扭转
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO statusConvertDTO = new ConvertActivityStatusDTO();//状态扭转对象，里面需要扭转什么
        statusConvertDTO.setActivityId(param.getActivityId());
        statusConvertDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        statusConvertDTO.setPrizeId(param.getPrizeId());
        statusConvertDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        statusConvertDTO.setUserIds(param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        statusConvertDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handleEvent(statusConvertDTO);
    }



//    /**
//     * 状态扭转
//     * @param param
//     */
//    private void statusConvert(DrawPrizeParam param) {
//
//            //存在的问题
//            //1.活动扭转有依赖性,代码维护性差
//            //2.扭转状态条件肯会扩展，当前写法扩展性、维护性差
//            //3.通过责任链设计模式和策略模式可以解决
//        //活动：RUNNING --> COMPLETED  全部奖品抽完之后才改变状态
//        //奖品：INIT --> COMPLETED  是初始化的奖品才能扭转
//        //人员列表：INIT --> COMPLETED
//
//        //1.扭转奖品状态
//        //（1）查询活动关联的奖品信息
//        //（2）条件判断是否符合扭转奖品状态：判断当前状态是否  不是COMPLETED ，如果不是，才能扭转
//
//        //2.扭转人员状态
//        //（1）查询活动关联的人员信息
//        //（2）条件判断是否符合扭转人员状态：判断当前状态是否  不是COMPLETED ，如果不是，才能扭转
//
//
//        //3.扭转活动状态（需要在前面两个完成之后）
//        //（1）查询活动信息
//        //（2）条件判断是否符合扭转活动状态：判断当前状态是否  不是COMPLETED ，如果不是且全部奖品抽完之后，才能扭转

//        //4.更新缓存信息

//    }






}
